#include "ldm_privatequeue.h"
#include "reg_transfer.h"
#include "am_assert.h"
#include "slave.h"
#if defined(TARG_SW5)||defined(TARG_SW9)
#include <stdbool.h>
#endif

#if defined(_CIRCULAR_QUEUE) || defined(_SERIAL_QUEUE)

#ifdef LOCAL_MULTI_PRIORI
__thread_local_fix unsigned short top[LOCAL_MULTI_SIZE]={0};  //available
__thread_local_fix unsigned short bottom[LOCAL_MULTI_SIZE]={0};
__thread_local_fix unsigned short ldmq_full[LOCAL_MULTI_SIZE]={0};
__thread_local_fix struct task* ldmqueue[LOCAL_MULTI_SIZE][LDM_QUEUE_SIZE]__attribute__((aligned(32)));

#ifdef ACEMESH_SCHEDULER_PROFILING
__thread_local_fix unsigned short max_ldmq[LOCAL_MULTI_SIZE] = {0};
__thread_local_fix long num_fulls[LOCAL_MULTI_SIZE] = {0};
#endif

__thread_local_fix int mqueue_empty[LOCAL_MULTI_SIZE]={false}; //not empty at the beginning

#define ldmq_empty(dest_qid) (ldmq_full[dest_qid]?0:((top[dest_qid]==bottom[dest_qid])?1:0))


#ifdef _CIRCULAR_QUEUE
extern __thread_local_fix struct task **my_mqueue_base[LOCAL_MULTI_SIZE];
#else
extern __thread_local_fix struct aceMesh_stack *my_private_queue[LOCAL_MULTI_SIZE];
#endif


#else
__thread_local_fix unsigned short top=0;  //available
__thread_local_fix unsigned short bottom=0;
__thread_local_fix unsigned short ldmq_full=0;
__thread_local_fix struct task* ldmqueue[LDM_QUEUE_SIZE]__attribute__((aligned(32)));

#ifdef ACEMESH_SCHEDULER_PROFILING
__thread_local_fix unsigned short max_ldmq = 0;
__thread_local_fix long num_fulls = 0;
#endif

__thread_local_fix int mqueue_empty=false; //not empty at the beginning


#define ldmq_empty (ldmq_full?0:((top==bottom)?1:0))

#ifdef _CIRCULAR_QUEUE
extern __thread_local_fix struct task **my_mqueue_base;
#else
extern __thread_local_fix struct aceMesh_stack *my_private_queue;
#endif


#endif

extern __thread_local_fix int my_id;





#ifdef _LDMQ
#ifdef LOCAL_MULTI_PRIORI
void spill2mmqueue(int dest_qid)
{
  //now spill one
  //TODO: OPT? spill more?
  struct task *t=ldmqueue[dest_qid][bottom[dest_qid]];
  bottom[dest_qid]=(bottom[dest_qid]+1)%LDM_QUEUE_SIZE;
  AM_output1(spill..)
  ldmq_full[dest_qid]=0; //not full now
#ifdef _SERIAL_QUEUE
  slave_push(my_private_queue[dest_qid],t);
#else
  slave_push(my_mqueue_base[dest_qid],t);
#endif
  mqueue_empty[dest_qid]=false;

  return;
}

#else
void spill2mmqueue()
{
  //now spill one
  //TODO: OPT? spill more?
  struct task *t=ldmqueue[bottom];
  bottom=(bottom+1)%LDM_QUEUE_SIZE;
  AM_output1(spill..)
  ldmq_full=0; //not full now
#ifdef _SERIAL_QUEUE
  slave_push(my_private_queue,t);
#else
  slave_push(my_mqueue_base,t);
#endif
  mqueue_empty=false;

  return;
}
#endif

#ifdef ACEMESH_PROFILING_SUCC
//extern unsigned long all_spawn_slave_task_time[65];
//extern unsigned long all_spawn_slave_trans1_time[65];
extern unsigned long all_spawn_slave_trans2_time[65];
extern __thread_local_fix unsigned long local_spawn_slave_trans2_time;
//extern unsigned long all_spawn_master_task_time[65];
#endif



#ifdef LOCAL_MULTI_PRIORI
void slave_ldmq_push(struct task* t)
{
  int i,tmp;
  int dest_qid=t->priority_id;
  if(ldmq_full[dest_qid]){
  #if defined(_CIRCULAR_QUEUE) 
   slave_bulkspill(my_mqueue_base[dest_qid]);
  #else  	
   spill2mmqueue(t->priority_id);
  #endif
  }
  //now we have space to push
  //AM_output1(ldmq_push...);
  ldmqueue[dest_qid][top[dest_qid]]=t;
  #ifdef DEBUG
  printf("push task(%p) to %d,%d\n",t,_MYID,t->affinity_id);
  #endif
  #ifdef ACEMESH_SCHEDULER_PROFILING
  tmp=(top[dest_qid]-bottom[dest_qid]+LDM_QUEUE_SIZE)%LDM_QUEUE_SIZE;
  max_ldmq[dest_qid]=(tmp>max_ldmq[dest_qid])?tmp:max_ldmq[dest_qid];
  #endif
  top[dest_qid]=(top[dest_qid]+1)%LDM_QUEUE_SIZE;
  if (top[dest_qid]==bottom[dest_qid]) {
     ldmq_full[dest_qid]=1;
  #if defined( ACEMESH_SCHEDULER_PROFILING) // && defined(_LDMQ)
     max_ldmq[dest_qid]=LDM_QUEUE_SIZE;
     num_fulls[dest_qid]++;
     if(my_id==0) AM_output(nfull:%ld,  num_fulls[dest_qid]);
  #endif
  }
  return;
}

#else
void slave_ldmq_push(struct task* t)
{
  #ifdef ACEMESH_PROFILING_SUCC
  unsigned long push_start=rtc();
  #endif
  int i,tmp;
  if(ldmq_full){
  #if defined(_CIRCULAR_QUEUE) 
   slave_bulkspill(my_mqueue_base);
  #else  	
   spill2mmqueue();
  #endif
  }
  //now we have space to push
  //AM_output1(ldmq_push...);
  ldmqueue[top]=t;
  #ifdef DEBUG
  printf("push task(%p) to %d,%d\n",t,_MYID,t->affinity_id);
  #endif
  #ifdef ACEMESH_SCHEDULER_PROFILING
  tmp=(top-bottom+LDM_QUEUE_SIZE)%LDM_QUEUE_SIZE;
  max_ldmq=(tmp>max_ldmq)?tmp:max_ldmq;
  #endif
  top=(top+1)%LDM_QUEUE_SIZE;
  if (top==bottom) {
     ldmq_full=1;
  #if defined( ACEMESH_SCHEDULER_PROFILING) // && defined(_LDMQ)
     max_ldmq=LDM_QUEUE_SIZE;
     num_fulls++;
     if(my_id==0) AM_output(nfull:%ld,  num_fulls);
  #endif
  }
  #ifdef ACEMESH_PROFILING_SUCC
  local_spawn_slave_trans2_time+=rtc()-push_start;
  #endif
  return;
} 
#endif

/*int slave_ldmq_trypop(struct task * * t)
{
  *t=NULL;
  if(!ldmq_empty){
    top=(top -1+LDM_QUEUE_SIZE)%LDM_QUEUE_SIZE;
    *t=ldmqueue[top];
    //AM_output1(ldmq_pop...);
    ldmq_full=0;
    return 1;
  }
  return 0; //pop null
}
*/
#endif
#endif
